/* 
    SAA7113 - Philips video decoder driver

    Copyright (C) 2000 Ralph Metzler <ralph@xxxxxxxxxxxxxx>
                  for Convergence Integrated Media GmbH

    based on the SAAA7110 and SAA7111 drivers by:

    Copyright (C) 1998 Pauline Middelink <middelin@xxxxxxxxxxx>

    Copyright (C) 1998 Dave Perks <dperks@xxxxxxx>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/    

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/wrapper.h>
#include <linux/video_decoder.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/version.h>
#include <linux/i2c.h>
#include <linux/videodev.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/page.h>
#include <asm/segment.h>

#include "saa7113-new.h"


MODULE_AUTHOR("Ralph Metzler");
MODULE_DESCRIPTION("Philips SAA7113 VideoStream Decoder Device Driver for Linux");
MODULE_LICENSE("GPL");


#define I2C_SAA7113        0x4a /* or 0x48 */

#define DEBUG(x)			/* remove when no long debugging */

#define	I2C_DELAY		10	/* 10 us or 100khz */


static struct i2c_client client_template;

struct saa7113 {
        struct i2c_client *client;
        int addr;
	unsigned char reg[0x62];

	int norm;
	int input;
	int enable;
	int bright;
	int contrast;
	int hue;
	int sat;
};

static int writereg(struct i2c_client *client, 
                    unsigned char reg, unsigned char data)
{
        int ret;
        unsigned char msg[] = {0x1f, 0x00};
        
        msg[0]=reg; msg[1]=data;
        ret=i2c_master_send(client, msg, 2);
        if (ret!=2) 
                printk("writereg error\n");
        ((struct saa7113 *) client->data)->reg[reg]=data;
        return ret;
}

static int writeregs(struct i2c_client *client, const unsigned char *regs)
{
        unsigned char reg, data;

        while (*regs!=0xff) {
                reg =*(regs++);
                data=*(regs++);
                if (writereg(client, reg, data)<0)
                        return -1;
        }
        return 0;
}

static u8 readreg(struct i2c_client *client, unsigned char reg)
{
        struct i2c_adapter *adap=client->adapter;
        unsigned char mm1[] = {0x1e};
        unsigned char mm2[] = {0x00};
        struct i2c_msg msgs[2];
        
        msgs[0].flags=0;
        msgs[1].flags=I2C_M_RD;
        msgs[0].addr=msgs[1].addr=client->addr;
        mm1[0]=reg;
        msgs[0].len=1; msgs[1].len=1;
        msgs[0].buf=mm1; msgs[1].buf=mm2;
        i2c_transfer(adap, msgs, 2);
        
        return mm2[0];
}

static const unsigned char init_saa7113[] = {
        0x01, 0x08, 
        0x02, 0xc0, /* c7 s-video */ 
        0x03, 0x23, 
        0x04, 0x00, 
        0x05, 0x00, 
        0x06, 0xeb,
        0x07, 0xe0,

        0x08, 0x88, 
        0x09, 0x00, 
        0x0a, 0x80, 
        0x0b, 0x47, 
        0x0c, 0x40, 
        0x0d, 0x00,
        0x0e, 0x01, 
        0x0f, 0xaa,
        
        0x10, 0x00, 
        0x11, 0x1C, 
        //0x12, 0x01, 
        //0x12, 0xe7, 
        0x12, 0xa7, 
        0x13, 0x00,
        0x15, 0x00,
        0x16, 0x00, 
        0x17, 0x00,

        0x40, 0x82,
        0x58, 0x00,
        0x59, 0x54,
        0x5a, 0x0a,
        0x5b, 0x83,
        0x5e, 0x00,

        0xff
};


void init(struct i2c_client *client)
{
        struct saa7113 *decoder=(struct saa7113 *) client->data;

	decoder->addr = client->addr;
	decoder->norm = VIDEO_MODE_AUTO;
	decoder->input = 0;
	decoder->enable = 1;
	decoder->bright = 32768;
	decoder->contrast = 32768;
	decoder->hue = 32768;
	decoder->sat = 32768;

        decoder->client=client;

        writeregs(client, init_saa7113);
        printk("saa7113: status=%02x\n", readreg(client, 0x1f));
}


int attach_adapter(struct i2c_adapter *adap)
{
        struct saa7113 *decoder;
        struct i2c_client *client;
        u8 version;
        
        client_template.adapter=adap;

        if (i2c_master_send(&client_template,NULL,0))
                return -1;
        
        client_template.adapter=adap;
        
        version=readreg(&client_template, 0x00);
        printk("saa7113: version=%02x\n", version);

        if (NULL == (client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL)))
                return -ENOMEM;
        memcpy(client, &client_template, sizeof(struct i2c_client));
        
        client->data=decoder=kmalloc(sizeof(struct saa7113),GFP_KERNEL);
        if (decoder==NULL) {
                kfree(client);
                return -ENOMEM;
        }
        
	memset(decoder, 0, sizeof(struct saa7113));

        printk("saa7113: attaching SAA7113 at 0x%02x\n", (client->addr)<<1);
        i2c_attach_client(client);

        init(client);

        printk("saa7113: attached to adapter %s\n", adap->name);
        return 0;
}
/* ----------------------------------------------------------------------- */


int detach_client(struct i2c_client *client)
{
        i2c_detach_client(client);
        kfree(client->data);
        kfree(client);
        return 0;
}

static int saa7113_command(struct i2c_client *client, unsigned int cmd, void *arg)
{
        struct saa7113 *decoder= (struct saa7113 *) client->data;
        int v;
                
        switch (cmd) {

        case DECODER_GET_CAPABILITIES:
                {
                        struct video_decoder_capability *dc = arg;
			dc->flags = VIDEO_DECODER_PAL
				  | VIDEO_DECODER_NTSC
				  | VIDEO_DECODER_SECAM
				  | VIDEO_DECODER_AUTO
				  | VIDEO_DECODER_CCIR;
			dc->inputs = 2;
			dc->outputs = 1;
		}
		break;

	case DECODER_GET_STATUS:
		{
			int *iarg = arg;
			int status;
			int res;

			status = readreg(client, 0x1f);
			res = 0;
			if ((status & (1 << 6)) == 0) {
				res |= DECODER_STATUS_GOOD;
			}
			switch (decoder->norm) {
			case VIDEO_MODE_NTSC:
				res |= DECODER_STATUS_NTSC;
				break;
			case VIDEO_MODE_PAL:
				res |= DECODER_STATUS_PAL;
				break;
			default:
			case VIDEO_MODE_AUTO:
				if ((status & (1 << 5)) != 0) {
					res |= DECODER_STATUS_NTSC;
				} else {
					res |= DECODER_STATUS_PAL;
				}
				break;
			}
			if ((status & (1 << 0)) != 0) {
				res |= DECODER_STATUS_COLOR;
			}
			*iarg = res;
		}
		break;


        case DECODER_SET_NORM:
                v = *(int*)arg;
                 
                switch (v) {
                case VIDEO_MODE_NTSC:
                        writereg(client, 0x08, 
                                 (decoder->reg[0x08] & 0x3f) | 0x40);
                        break;
                        
                case VIDEO_MODE_PAL:
                        writereg(client, 0x08,
                                 (decoder->reg[0x08] & 0x3f) | 0x00);
                        break;
                        
                case VIDEO_MODE_AUTO:
                        writereg(client, 0x08, 
                                 (decoder->reg[0x08] & 0x3f) | 0x80);
                        break;
                        
                default:
                  return -EINVAL;
                  
                }
                decoder->norm = v;
                
                break;

	case DECODER_SET_INPUT:
		{
			int *iarg = arg;

			if (*iarg < 0 || *iarg > 7) {
				return -EINVAL;
			}
			if (decoder->input != *iarg) {
				decoder->input = *iarg;
				/* select mode */
				writereg(client, 0x02, 
                                         (decoder->reg[0x02] & 0xf8) | decoder->input);
				/* bypass chrominance trap for modes 4..7 */
				writereg(client, 0x09, 
                                         (decoder->reg[0x09] & 0x7f) | 
                                         ((decoder->input > 3) ? 0x80 : 0));
			}
		}
		break;


	 case DECODER_SET_OUTPUT:
		v = *(int*)arg;
		/* not much choice of outputs */
		if (v != 0)
			return -EINVAL;
		break;

	case DECODER_ENABLE_OUTPUT:
		{
			int *iarg = arg;
			int enable = (*iarg != 0);

			if (decoder->enable != enable) {
				decoder->enable = enable;

// RJ: If output should be disabled (for playing videos), we also need a open PLL.
				//     The input is set to 0 (where no input source is connected), although this
				//     is not necessary.
				//
				//     If output should be enabled, we have to reverse the above.

				if (decoder->enable) {
					writereg(client, 0x02, 
                                                 (decoder->reg[0x02] & 0xf8) | 
                                                 decoder->input);
					writereg(client, 0x08, 
                                                 (decoder->reg[0x08] & 0xfb));
					writereg(client, 0x11, (decoder->reg[0x11] & 0xf3) | 0x0c);
				} else {
					writereg(client, 0x02, (decoder->reg[0x02] & 0xf8));
					writereg(client, 0x08, (decoder->reg[0x08] & 0xfb) | 0x04);
					writereg(client, 0x11, (decoder->reg[0x11] & 0xf3));
				}
			}
		}
		break;

	case DECODER_SET_PICTURE:
		{
			struct video_picture *pic = arg;

			if (decoder->bright != pic->brightness) {
				/* We want 0 to 255 we get 0-65535 */
				decoder->bright = pic->brightness;
				writereg(client, 0x0a, decoder->bright >> 8);
			}
			if (decoder->contrast != pic->contrast) {
				/* We want 0 to 127 we get 0-65535 */
				decoder->contrast = pic->contrast;
				writereg(client, 0x0b, decoder->contrast >> 9);
			}
			if (decoder->sat != pic->colour) {
				/* We want 0 to 127 we get 0-65535 */
				decoder->sat = pic->colour;
				writereg(client, 0x0c, decoder->sat >> 9);
			}
			if (decoder->hue != pic->hue) {
				/* We want -128 to 127 we get 0-65535 */
				decoder->hue = pic->hue;
				writereg(client, 0x0d, (decoder->hue - 32768) >> 8);
			}
		}
		break;

	 default:
                printk(KERN_INFO "unknown saa7113_command??(%d)\n",cmd);
		return -EINVAL;
        }
        
        return 0;
} 


void inc_use (struct i2c_client *client)
{
#ifdef MODULE
        MOD_INC_USE_COUNT;
#endif
}

void dec_use (struct i2c_client *client)
{
#ifdef MODULE
        MOD_DEC_USE_COUNT;
#endif
}

static struct i2c_driver saa7113_driver = {
        "SAA7113",
        I2C_DRIVERID_SAA7113,
        I2C_DF_NOTIFY,
        attach_adapter,
        detach_client,
        saa7113_command,
        inc_use,
        dec_use,
};

static struct i2c_client client_template = {
        "SAA7113",
        I2C_DRIVERID_SAA7113,
        0,
        (0x4a >> 1),
        NULL,
        &saa7113_driver,
        NULL
};

EXPORT_NO_SYMBOLS;

#ifdef MODULE
int init_module(void)
#else
int saa7113_init(void)
#endif
{
        int res;
        
        if ((res = i2c_add_driver(&saa7113_driver))) 
        {
                printk("saa7113: Driver registration failed, module not inserted.\n");
                return res;
        }
        
        printk("saa7113: init_module\n");
        return 0;
}

#ifdef MODULE
void cleanup_module(void)
{
        int res;
        
        if ((res = i2c_del_driver(&saa7113_driver))) 
        {
                printk("saa7113: Driver deregistration failed, "
                       "module not removed.\n");
        }
}
#endif



